#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include <vector>

#include <boost/scoped_array.hpp>

#include <GL/glu.h>
#include <GL/gl.h>

#include <wx/dcclient.h>
#include <wx/menu.h>
#include <wx/msgdlg.h>

#include <Klein/Application/GLCanvas.h>
#include <Klein/Application/SelectNodeClientData.h>
#include <Klein/Application/WorkspaceFrame.h>
#include <Klein/Shader/FragmentDatabase.h>
#include <Klein/Shader/GraphDatabase.h>

GLCanvas::GLCanvas(wxWindow* parent, wxWindowID id, int* args) :
	wxGLCanvas(parent, id, args, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
{
	mContext = new wxGLContext(this);
	wxGLCanvas::SetCurrent(*mContext);

	SetBackgroundStyle(wxBG_STYLE_CUSTOM);
}

GLCanvas::~GLCanvas()
{
	if (mFragmentMenu.get())
	{
		mFragmentMenu->Disconnect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GLCanvas::OnFragmentMenuClick), NULL, this);
	}
	delete mContext;
}

BEGIN_EVENT_TABLE(GLCanvas, wxGLCanvas)
	EVT_MOTION(GLCanvas::OnMotion)
	EVT_LEFT_DOWN(GLCanvas::OnLeftDown)
	EVT_RIGHT_DOWN(GLCanvas::OnRightDown)

	EVT_PAINT(GLCanvas::OnPaint)
	EVT_SIZE(GLCanvas::OnResize)
END_EVENT_TABLE()

void GLCanvas::OnMotion(wxMouseEvent& event)
{
	if  (event.ButtonIsDown(wxMOUSE_BTN_LEFT) && event.Dragging())
	{
		Render(true);
	}
}

void GLCanvas::OnLeftDown(wxMouseEvent& event)
{
	mLeftDownPosition = event.GetPosition();
	mLeftDownPosition.y = GetClientSize().y - mLeftDownPosition.y;
	Render(true);
}

void GLCanvas::OnRightDown(wxMouseEvent& event)
{
	//
	FragmentDatabase& fragmentDatabase = FragmentDatabase::GetReference();

	const String& activedFragmentCategoryName = fragmentDatabase.GetActivedFragmentCategoryName();
	std::set<String> fragmentClassNames;
	fragmentDatabase.CollectFragmentClassNames(activedFragmentCategoryName, fragmentClassNames);

	//
	mFragmentMenu.reset(new wxMenu);
	mFragmentMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(GLCanvas::OnFragmentMenuClick), NULL, this);
	mFragmentMenu->SetTitle(activedFragmentCategoryName);

	//
	int menuItemNumber = 0;
	for (std::set<String>::const_iterator cItr = fragmentClassNames.begin(); cItr != fragmentClassNames.end(); ++ cItr)
	{
		wxMenu* fragmentSubMenu = new wxMenu;

		std::vector<FragmentRef> nodes;
		fragmentDatabase.CollectFragments(*cItr, nodes);
		for (std::vector<FragmentRef>::const_iterator nItr = nodes.begin(); nItr != nodes.end(); ++ nItr)
		{
			const FragmentIdentifier& fragmentIdentifier = (*nItr)->GetIdentifier();
			fragmentSubMenu->Append(menuItemNumber, fragmentIdentifier.GetName().c_str());
			mFragmentMenuItemNumbersToFragmentIdentifiers.insert(std::make_pair(menuItemNumber, fragmentIdentifier));
			menuItemNumber += 1;
		}
		mFragmentMenu->AppendSubMenu(fragmentSubMenu, *cItr);
	}

	mRightDownPosition = event.GetPosition();
	mRightDownPosition.y = GetClientSize().y - mRightDownPosition.y;

	PopupMenu(mFragmentMenu.get());
}

void GLCanvas::OnPaint(wxPaintEvent& event)
{
	wxPaintDC(this);

	//
	Render(false);
}

void GLCanvas::OnResize(wxSizeEvent& event)
{
	wxGLCanvas::OnSize(event);

	//
	wxGLCanvas::SetCurrent(*mContext);

	//
	wxSize size(event.GetSize());

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0, size.GetWidth(), 0, size.GetHeight());

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	glViewport(0, 0, size.GetWidth(), size.GetHeight());

	//
	assert(glGetError() == GL_NO_ERROR);
}

void GLCanvas::OnFragmentMenuClick(wxCommandEvent& event)
{
	std::map<int, FragmentIdentifier>::const_iterator itr = mFragmentMenuItemNumbersToFragmentIdentifiers.find(event.GetId());
	assert(itr != mFragmentMenuItemNumbersToFragmentIdentifiers.end());

	GraphDatabase& graphDatabase = GraphDatabase::GetReference();
	GraphRef activedGraph = graphDatabase.GetActivedGraph();
	assert(activedGraph);

	assert(itr->second.GetCategoryName() == activedGraph->GetIdentifier().GetCategoryName());

	//
	NodeRef newNode = activedGraph->CreateNode(itr->second);
	assert(newNode);

	newNode->SetPosition(mRightDownPosition.x, mRightDownPosition.y);

	//
	Refresh();
}

void GLCanvas::Render(const bool selectionMode)
{
	GraphDatabase& graphDatabase = GraphDatabase::GetReference();

	//
	GraphRef activedGraph = graphDatabase.GetActivedGraph();
	std::vector<NodeRef> graphNodesInActivedGraph;
	activedGraph->CollectNodes(graphNodesInActivedGraph);

	boost::scoped_array<GLuint> glSelectionBuffer;
	if (selectionMode)
	{
		int glSelectionBufferLength = (graphNodesInActivedGraph.size() + 1) * 4;
		glSelectionBuffer.reset(new GLuint[glSelectionBufferLength]);
		std::fill_n(glSelectionBuffer.get(), glSelectionBufferLength, -1);
		glSelectBuffer(glSelectionBufferLength, glSelectionBuffer.get());
		glRenderMode(GL_SELECT);
		glInitNames();
		glPushName(0);

		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();

		GLint viewport[4];
		glGetIntegerv(GL_VIEWPORT,viewport);
		gluPickMatrix(mLeftDownPosition.x, mLeftDownPosition.y, 2, 2, viewport);
		gluOrtho2D(0, GetClientSize().GetWidth(), 0, GetClientSize().GetHeight());

		assert(glGetError() == GL_NO_ERROR);
	}else
	{
		glRenderMode(GL_RENDER);

		glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
		glClearDepth(1.0f);
		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	}

	for (std::vector<NodeRef>::const_iterator itr = graphNodesInActivedGraph.begin(); itr != graphNodesInActivedGraph.end(); ++ itr)
	{
		const NodeRef graphNode = *itr;

		glLoadName(graphNode->GetNumber());

		//
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glTranslatef(graphNode->GetPositionX(), graphNode->GetPositionY(), 0);
			glBegin(GL_QUADS);
				glColor3ub(255, 255, 255);glVertex2i(0,      0);
				glColor3ub(255, 255, 255);glVertex2i(0,   -250);
				glColor3ub(255,   0,   0);glVertex2i(150, -250);
				glColor3ub(255,   0,   0);glVertex2i(150,    0);
			glEnd();
		
		const std::map<String, Parameter>& parameterGroup = graphNode->GetParameterGroup()->GetParameters();
		int rows[2] = {0, 0};
		for (std::map<String, Parameter>::const_iterator itr = parameterGroup.begin(); itr != parameterGroup.end(); ++ itr)
		{
			glMatrixMode(GL_MODELVIEW);
			glPushMatrix();
			if (itr->second.GetIOType() == Parameter::IO_INPUT)
			{
				glTranslatef(0, rows[0] * (-50), 0);

				glBegin(GL_QUADS);
				glColor3ub(0, 255, 0);
				glVertex2i(0,  0);
				glVertex2i(0,  -50);
				glVertex2i(50, -50);
				glVertex2i(50, 0);
				glEnd();

				rows[0] += 1;
			}
			else
			{
				glTranslatef(100, rows[1] * (-50), 0);

				glBegin(GL_QUADS);
				glColor3ub(0, 0, 255);
				glVertex2i(0,  0);
				glVertex2i(0,  -50);
				glVertex2i(50, -50);
				glVertex2i(50, 0);
				glEnd();

				rows[1] += 1;
			}

			glMatrixMode(GL_MODELVIEW);
			glPopMatrix();
		}

		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
	}

	if (selectionMode)
	{
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glFlush();

		//
		GLint hitCount = glRenderMode(GL_RENDER);
		if (hitCount > 0)
		{
			GLuint distance = 0xFFFFFFFFU;
			GLuint closestNodeNumber = -1;
			for (GLint i = 0; i < hitCount; ++ i)
			{
				if (glSelectionBuffer[(hitCount - 1) * 4 + 1] < distance)
				{
					distance = glSelectionBuffer[(hitCount - 1) * 4 + 1];
					closestNodeNumber = glSelectionBuffer[(hitCount - 1) * 4 + 3];
				}
			}

			SelectNodeClientData* selectNodeClientData = new SelectNodeClientData(closestNodeNumber, activedGraph->GetActivedNodeNumber());
			wxCommandEvent commandEvent(wxEVT_COMMAND_BUTTON_CLICKED, wxID_WORKSPACE_CANVAS);
			commandEvent.SetClientObject(selectNodeClientData);
			wxPostEvent(GetParent()->GetEventHandler(), commandEvent);
		}
	}else
	{
		SwapBuffers();
	}

	assert(glGetError() == GL_NO_ERROR);
}
